// This is the main DLL file.

#include "stdafx.h"
#include "managedESG.h"
#include "exceptions.h"
#include "windows.h"
#include "..\Sustainalytics.ESGRatings.Cuda\include\cudaEsg.h"

namespace Sustainalytics
{
	namespace ESGRatings
	{
		namespace ManagedCuda
		{

#pragma region Constructor/Destructor/Finalize

			ESGContext::ESGContext(){
				try {
					_bufferManager = gcnew Primitives::BufferManager();
					_factory = new cudaEsg::ComputeContextFactory();
					_context = _factory->CreateContext().release();
					_memoryStatistics = gcnew Primitives::Statistics();
				}
				catch (cudaEsg::ComputeContextException& ce)
				{
					throw CreateManagedException(ce.operation_error);
				}
			}

			ESGContext::~ESGContext() {
				if (_disposed)
					return;
				_disposed = true;
				try {
					delete _bufferManager; // dispose
					delete _memoryStatistics;
				}
				finally {
					this->!ESGContext();
				}
			}

			ESGContext::!ESGContext() {
				delete _factory;
				delete _context;
				
			}

#pragma endregion 

#pragma region Ratings

			Primitives::RatingsResult^ ESGContext::Ratings(int setId, int universeId, int matrixId, Byte filter, array<int>^ groupByHeads, bool computeIndicatorsRatings, bool returnAggregatedWeights, bool returnWeights, bool isDefaultWeightMatrix){

				int companiesCount;
				int indicatorsCount;
				int clusterGroupsCount;
				int clustersCount;

				cudaEsg::operation_errors_t result = _context->FillSetAttributes(setId, companiesCount, indicatorsCount, clusterGroupsCount, clustersCount);
				clusterGroupsCount = groupByHeads->Length+1; //TODO: review here if correct
				if (result.esgError != cudaEsg::esgSuccess){
					throw CreateManagedException(result);
				}
				
				Primitives::RatingsResult^ primitiveResult = gcnew Primitives::RatingsResult(_bufferManager, companiesCount, indicatorsCount, clustersCount, clusterGroupsCount, computeIndicatorsRatings, returnAggregatedWeights, returnWeights, isDefaultWeightMatrix);

				try {

					primitiveResult->AllocateBuffers();

					if (companiesCount == 0)
						return primitiveResult;


					pin_ptr<int> indices = &(primitiveResult->CompanyIndices[0]);
					pin_ptr<float> ratings = &(primitiveResult->Ratings[0]);
					pin_ptr<bool> isInRefUniverse = &(primitiveResult->IsInReferenceUniverse[0]);
					pin_ptr<float> indicators_ratings = computeIndicatorsRatings ? &(primitiveResult->IndicatorsRatings[0]) : nullptr;
					pin_ptr<float> aggregated_weights = returnAggregatedWeights ? &(primitiveResult->AggregatedWeights[0]) : nullptr;
					pin_ptr<float> weights = returnWeights ? &(primitiveResult->Weights[0]) : nullptr;


					pin_ptr<float> group_avgs = &(primitiveResult->Group_Avgs[0]);
					pin_ptr<float> group_mins = &(primitiveResult->Group_Mins[0]);
					pin_ptr<float> group_maxs = &(primitiveResult->Group_Maxs[0]);
					pin_ptr<int> group_counts = &(primitiveResult->Group_Counts[0]);
					pin_ptr<int> rankings = &(primitiveResult->Rankings[0]);
					pin_ptr<float> percentiles = &(primitiveResult->Percentile[0]);


					cudaEsg::RatingsResult result =  cudaEsg::RatingsResult();

					result.companiesCount = (primitiveResult->CompaniesCount);
					result.clustersCount = (primitiveResult->ClustersCount);
					result.clusterGroupsCount = primitiveResult->ClusterGroupsCount;
					result.indicatorsCount = primitiveResult->IndicatorsCount;
					result.isInReferenceUniverse = isInRefUniverse;
					result.group_avgs = group_avgs;
					result.group_mins = group_mins;
					result.group_maxs = group_maxs;
					result.group_counts = group_counts;
					result.rankings = rankings;
					result.percentile = percentiles;

					result.indices = indices;
					result.ratings = ratings;
					result.indicators_ratings = indicators_ratings;
					result.aggregatedWeights = aggregated_weights;
					result.weights = weights;


					if (computeIndicatorsRatings) {
						result.options |= cudaEsg::ComputeIndicatorsRatingsOption;
					}


					if (returnAggregatedWeights)
					{
						result.options |= cudaEsg::ComputeAggregatedWeights;
					}


					if (returnWeights)
					{
						result.options |= cudaEsg::ReturnWeights;
					}

					if (isDefaultWeightMatrix)
					{
						result.options |= cudaEsg::DefaultWeightMatrix;
					}

					pin_ptr<int> pinHeads;
					if (groupByHeads  != nullptr && groupByHeads->Length > 0)
					{
						pinHeads = &groupByHeads[0];
					}


					int *groupsHeads = pinHeads;
					int groupsHeadsCount = groupByHeads->Length;


					cudaEsg::operation_errors_t error = _context->Ratings(setId, universeId, matrixId, filter, groupsHeadsCount, groupsHeads, result);

					if (error.esgError != cudaEsg::esgSuccess){
						throw CreateManagedException(error);
					}


					if (result.timedOperations) {

						auto op_it = result.timedOperations->begin();
						for (; op_it != result.timedOperations->end(); op_it++)
						{
							primitiveResult->TimedSteps->Add(gcnew Primitives::TimedStep(CreateString((*op_it)->name.c_str()), (*op_it)->elapsedTimeMilliseconds));
						}
					}


					return primitiveResult;

				}
				catch (...) {
					delete primitiveResult;
					throw;
				}



			}


			Primitives::GovernanceRatingsResult^  ESGContext::Ratings(int setId, int matrixId,bool includeWeightedScores, bool includeWeights)
			{
				int companiesCount;
				int indicatorsCount;
				int clusterGroupsCount;
				int clustersCount;
				int groupingCategoriesCount;

				cudaEsg::operation_errors_t result = _context->FillSetAttributes(setId, companiesCount, indicatorsCount, clusterGroupsCount, clustersCount,groupingCategoriesCount);
				if (result.esgError != cudaEsg::esgSuccess){
					throw CreateManagedException(result);
				}

				

				Primitives::GovernanceRatingsResult^ primitiveResult = gcnew Primitives::GovernanceRatingsResult(_bufferManager, companiesCount, indicatorsCount, clustersCount, groupingCategoriesCount);

				try {

					primitiveResult->AllocateBuffers();

					if (companiesCount == 0)
						return primitiveResult;


					pin_ptr<int> indices = &(primitiveResult->CompanyIndices[0]);
					pin_ptr<float> ratings = &(primitiveResult->Ratings[0]);
		


				
					pin_ptr<int> group_counts = &(primitiveResult->GroupCounts[0]);
					pin_ptr<int> rankings = &(primitiveResult->Rankings[0]);
					pin_ptr<float> percentiles = &(primitiveResult->Percentile[0]);

					pin_ptr<float> issueWeightedScores = includeWeightedScores ? &(primitiveResult->IssuesWeightedScores[0]) : nullptr;
					pin_ptr<float> weights = includeWeights ? &(primitiveResult->Weights[0]) : nullptr;



					cudaEsg::GovernanceRatingsResult result = cudaEsg::GovernanceRatingsResult(primitiveResult->CompaniesCount, clustersCount, primitiveResult->IndicatorsCount, primitiveResult->ComparativeGroupsCount, primitiveResult->IssuesCount);

					result.companiesCount = (primitiveResult->CompaniesCount);
					result.clustersCount = clustersCount;//todo: review this here
					result.clusterGroupsCount = primitiveResult->IssuesCount;
					result.indicatorsCount = primitiveResult->IndicatorsCount;
					result.groupingCategoriesCount = primitiveResult->ComparativeGroupsCount;
					
					result.group_counts = group_counts;
					result.rankings = rankings;
					result.percentile = percentiles;

					result.ratings = ratings;
					result.indices = indices;

					result.issue_Weighted_Scores = issueWeightedScores;
					result.weights = weights;

					cudaEsg::operation_errors_t error = _context->GovernanceRatings(setId,  matrixId, includeWeights, includeWeightedScores, result);

					if (error.esgError != cudaEsg::esgSuccess){
						throw CreateManagedException(error);
					}


					


					return primitiveResult;

				}
				catch (...) {
					delete primitiveResult;
					throw;
				}

			}



			CudaESGException^ ESGContext::CreateManagedException(cudaEsg::operation_errors_t& error){

				CudaESGException^ exception = gcnew CudaESGException(error.esgError, error.internalError);

				exception->InternalErrorMessage = CreateString(error.internalErrorString);
				exception->File = CreateString(error.file);
				exception->LineNumber = error.lineNo;
				exception->FunctionName = CreateString(error.functionName);

				return exception;
			}
			
			String^ ESGContext::CreateString(const char * sourceString){
				System::String^ internalDescription = "";
				try {
					if (sourceString != nullptr)
						internalDescription = gcnew System::String(sourceString);
				}
				catch (...){}

				return internalDescription;
			}

#pragma endregion 

#pragma region Update Sets

			int ESGContext::AddCompanySet( int companiesCount, int indicatorsCount, int clusterCount, array<byte>^ esgHeads, array<byte>^disjunctClustersHeads, int disjunctClustersCount, array<byte>^ disjunctClustersMap) {

				if (esgHeads == nullptr || esgHeads->Length == 0){
					throw gcnew Primitives::Exceptions::ESGException(cudaEsg::esgInvalidParameters,"esgHeads cannot be empty",__FILE__);
				}

				if (disjunctClustersHeads == nullptr || disjunctClustersHeads->Length == 0){
					throw gcnew Primitives::Exceptions::ESGException(cudaEsg::esgInvalidParameters, "disjunctClustersHeads cannot be empty", __FILE__);
				}

				if (disjunctClustersMap == nullptr || disjunctClustersMap->Length == 0){
					throw gcnew Primitives::Exceptions::ESGException(cudaEsg::esgInvalidParameters, "disjunctClustersMap cannot be empty", __FILE__);
				}


				pin_ptr<int> pinHeads = nullptr;

				pin_ptr<byte> pinEsgHeads = &esgHeads[0];
				pin_ptr<byte> pinDisjunctClustersHeads = &disjunctClustersHeads[0];
				pin_ptr<byte> pinDisjunctClustersMap = &disjunctClustersMap[0];
			
			
				
				cudaEsg::operation_errors_t status;

				cudaEsg::SetConfiguration* setConfiguration = new cudaEsg::SetConfiguration();
				
				setConfiguration->companiesCount = companiesCount;
				
				
				setConfiguration->esgHeads = pinEsgHeads;
				setConfiguration->esgHeadsCount = esgHeads->Length;

				setConfiguration->disjunctClustersHeads = pinDisjunctClustersHeads;
				setConfiguration->disjunctClustersHeadsCount = disjunctClustersHeads->Length;

				setConfiguration->disjunctClustersMap = pinDisjunctClustersMap;
				setConfiguration->disjunctClustersMapCount = disjunctClustersMap->Length;

				setConfiguration->indicatorsCount = indicatorsCount;
				setConfiguration->CLUSTERS_COUNT = clusterCount;
				setConfiguration->DISJUNCT_CLUSTERS_COUNT = disjunctClustersCount;


				status = _context->AddSet(setConfiguration);

				if (status.esgError != cudaEsg::esgSuccess) {
					throw CreateManagedException(status);
				}

				return setConfiguration->setId;
			}

			bool ESGContext::DeleteSet(int setId)
			{
				cudaEsg::operation_errors_t status;

				status = _context->DeleteSet(setId);

				//if (status.esgError != cudaEsg::esgSuccess) {
				//	throw CreateManagedException(status);
				//}

				return status.esgError == cudaEsg::esgSuccess;
			}

			array<int>^ ESGContext::CleanContext()
			{

				cudaEsg::operation_errors_t status;
				int* ids=NULL;
				int n=0;
				status = _context->CleanContext(ids,n);


				if (status.esgError != cudaEsg::esgSuccess) {
					throw CreateManagedException(status);
				}

				array<int>^ returnIds = gcnew array<int>(n);
				System::Runtime::InteropServices::Marshal::Copy(IntPtr((void *)ids), returnIds, 0, n);
				delete ids;
				ids = NULL;
				return returnIds;
				
			}

			void ESGContext::SetIndicators(int setId, int companiesCount, int indicatorsCount, array<float>^ indicatorScores){

				if (indicatorsCount <= 0){
					throw gcnew System::ArgumentException("indicatorsCount", "indicatorsCount <= 0");
				}

				if (indicatorScores == nullptr){
					throw gcnew System::ArgumentNullException("indicatorScores", "AddCompanySet");
				}

				if (indicatorsCount > 0 && indicatorsCount > indicatorScores->Length){
					throw gcnew System::ArgumentException("indicatorsCount", "indicatorScores > indicatorScores->Length");
				}

				pin_ptr<float> pinIndicators = &indicatorScores[0];
				
				cudaEsg::operation_errors_t status;
				status = _context->SetIndicators(setId, companiesCount, indicatorsCount, pinIndicators);

				if (status.esgError != cudaEsg::esgSuccess) {
					throw CreateManagedException(status);
				}
			}

			void ESGContext::AddMatrix(int setId, int matrixId, int weightsCount, array<float>^ weightsMatrix, int profilesCount, array<byte>^ profilesFlags){

				if (weightsCount <= 0){
					throw gcnew System::ArgumentException("weightsCount", "elementsCount <= 0");
				}

				if (weightsMatrix == nullptr){
					throw gcnew System::ArgumentNullException("weightsMatrix", "null weightsMatrix");
				}

				if (weightsCount > weightsMatrix->Length){
					throw gcnew System::ArgumentException("weightsCount", "weightsCount > weightsMatrix->Length");
				}

				if (profilesCount<=0)
				{
					throw gcnew System::ArgumentException("profilesCount", "profilesCount <= 0");
				}


				if (profilesFlags == nullptr){
					throw gcnew System::ArgumentNullException("profilesFlags", "null profilesFlags");
				}

				if (profilesCount > profilesFlags->Length){
					throw gcnew System::ArgumentException("weightsCount", "profilesCount > profilesFlags->Length");
				}
				pin_ptr<float> pinWeigths = &weightsMatrix[0];

				pin_ptr<byte> pinFlags = &profilesFlags[0];

				cudaEsg::operation_errors_t status;

				status = _context->AddMatrix(setId, matrixId, weightsCount, pinWeigths, profilesCount, pinFlags);

				if (status.esgError != cudaEsg::esgSuccess) {
					throw CreateManagedException(status);
				}
			}

			bool ESGContext::DeleteMatrix(int setId, int matrixId){

				cudaEsg::operation_errors_t status;

				status = _context->DeleteMatrix(setId, matrixId);

				return (status.esgError == cudaEsg::esgSuccess);
					
			}

			void ESGContext::AddUniverse(int setId, int universeId, int companiesCount, array<Byte>^ universe){

				if (companiesCount <= 0){
					throw gcnew System::ArgumentException("companiesCount", "companiesCount <= 0");
				}

				if (universe == nullptr){
					throw gcnew System::ArgumentNullException("universe", "null universe");
				}

				if (companiesCount > universe->Length){
					throw gcnew System::ArgumentException("companiesCount", "companiesCount > universe->Length");
				}


				pin_ptr<unsigned char> pinUniverse = &universe[0];

				cudaEsg::operation_errors_t status;
			
				status = _context->AddUniverse(setId, universeId, companiesCount, pinUniverse);

				if (status.esgError != cudaEsg::esgSuccess) {
					throw CreateManagedException(status);
				}
			}

			bool ESGContext::DeleteReferenceUniverse(int setId, int universeId){

				cudaEsg::operation_errors_t status;

				status = _context->DeleteUniverse(setId, universeId);

				return (status.esgError != cudaEsg::esgSuccess);
			}

			void ESGContext::SetAttributes(int setId, int companiesCount, array<Byte>^ attributes){

				if (companiesCount <= 0){
					throw gcnew System::ArgumentException("companiesCount", "companiesCount <= 0");
				}

				if (attributes == nullptr){
					throw gcnew System::ArgumentNullException("attributes", "null attributes");
				}

				if (companiesCount > attributes->Length){
					throw gcnew System::ArgumentException("companiesCount", "companiesCount > attributes->Length");
				}

				pin_ptr<unsigned char> pinAddress = &attributes[0];

				cudaEsg::operation_errors_t status;

				status = _context->SetAttributes(setId, companiesCount, pinAddress);

				if (status.esgError != cudaEsg::esgSuccess) {
					throw CreateManagedException(status);
				}
			}

			void ESGContext::SetCompanyScores(int setId, int scoresNo, array<float>^ scoresData, array<float>^ weightsData)
			{
				pin_ptr<float> pinScores = &scoresData[0];

				pin_ptr<float> pinWeights = &weightsData[0];


				cudaEsg::operation_errors_t status;

				status = _context->SetCompaniesScores(setId, scoresNo, pinScores);

				if (status.esgError != cudaEsg::esgSuccess) {
					throw CreateManagedException(status);
				}

				status = _context->SetCompaniesWeights(setId, scoresNo, pinWeights);

				if (status.esgError != cudaEsg::esgSuccess) {
					throw CreateManagedException(status);
				}
			}

			void ESGContext::SetAttributes(int setId, int companiesCount, int attributesTypesCount, array<int>^ regionGroupArray, int regionGroupsCount, array<int>^ peerGroupArray, int peerGroupsCount)
			{

				if (companiesCount <= 0){
					throw gcnew System::ArgumentException("companiesCount", "companiesCount <= 0");
				}

				if (regionGroupArray == nullptr || peerGroupArray==nullptr){
					throw gcnew System::ArgumentNullException("attributes", "null attributes");
				}

				if (companiesCount > regionGroupArray->Length){
					throw gcnew System::ArgumentException("companiesCount", "companiesCount > attributes->Length");
				}	

				if (companiesCount > peerGroupArray->Length){
					throw gcnew System::ArgumentException("companiesCount", "companiesCount > attributes->Length");
				}

				array<int> ^ mergedAttributes = gcnew array<int>(regionGroupArray->Length + peerGroupArray->Length);
				regionGroupArray->CopyTo(mergedAttributes, 0);
				peerGroupArray->CopyTo(mergedAttributes, regionGroupArray->Length);

				pin_ptr<int> pinAddress = &mergedAttributes[0];

				cudaEsg::operation_errors_t status;

				status = _context->SetCompanyGroupFlags(setId, companiesCount, attributesTypesCount,  regionGroupsCount, peerGroupsCount, pinAddress, mergedAttributes->Length);

				if (status.esgError != cudaEsg::esgSuccess) {
					throw CreateManagedException(status);
				}


				//std::vector<cudaEsg::ComparativeGroup, std::allocator<cudaEsg::ComparativeGroup >> pinAddress;

				////TODO Mada;ina: refactor this
				//	cudaEsg::ComparativeGroup itemPeerGroup =  cudaEsg::ComparativeGroup();
				//	pin_ptr<int> pinRegions =&peerGroupArray[0];
				//	itemPeerGroup.flags = pinRegions;
				//	pinAddress.push_back(itemPeerGroup);

				//	cudaEsg::ComparativeGroup item =  cudaEsg::ComparativeGroup();
				//	pin_ptr<int> pinWeigths =&regionGroupArray[0];
				//	item.flags = pinWeigths;
				//	pinAddress.push_back(item);
				//

				//cudaEsg::operation_errors_t status;

				//status = _context->SetCompanyGroupFlags(setId, companiesCount, attributesTypesCount, pinAddress );

				//if (status.esgError != cudaEsg::esgSuccess) {
				//	throw CreateManagedException(status);
				//}
			}

			void ESGContext::SetCombinationModifiers(int setId, int matrixId, int weightsCount, array<float>^ weightData)
			{
				if (weightsCount <= 0){
					throw gcnew System::ArgumentException("weightsCount", "elementsCount <= 0");
				}

				if (weightData == nullptr){
					throw gcnew System::ArgumentNullException("weightData", "null weightData");
				}

				if (weightsCount > weightData->Length){
					throw gcnew System::ArgumentException("weightData", "weightData > weightsMatrix->Length");
				}


				pin_ptr<float> pinWeigths = &weightData[0];


				cudaEsg::operation_errors_t status;

				status = _context->SetCombinationModifiers(setId, matrixId, weightsCount, pinWeigths);

				if (status.esgError != cudaEsg::esgSuccess) {
					throw CreateManagedException(status);
				}
			}

			void ESGContext::SetIssueWeights(int setId, int matrixId, int weightsCount, array<float>^ weightData)
			{
				
				if (weightsCount <= 0){
					throw gcnew System::ArgumentException("weightsCount", "elementsCount <= 0");
				}

				if (weightData == nullptr){
					throw gcnew System::ArgumentNullException("weightData", "null weightData");
				}

				if (weightsCount > weightData->Length){
					throw gcnew System::ArgumentException("weightData", "weightData > weightsMatrix->Length");
				}


				pin_ptr<float> pinWeigths = &weightData[0];


				cudaEsg::operation_errors_t status;

				status = _context->SetIssueWeights(setId, matrixId, weightsCount, pinWeigths);

				if (status.esgError != cudaEsg::esgSuccess) {
					throw CreateManagedException(status);
				}
			}
#pragma endregion 

		}
	}
}